<template>
  <div class="data-flow-design" @contextmenu="(event) => event.preventDefault()">
    <div class="function-type-list">
      <operator-type-node4-list
        v-for="(item, index) of operatorTypeInfoMapping.entries()"
        :key="index"
        :name="item[1].name"
        :type="item[0]"
        draggable="true"
        @dragstart="dragStart"
        @dragend="addFunctionTypeNode2OperationSpace"
      />
      <span>
        {{ dataFlowState}}
      </span>
    </div>
    <div
      ref="operationSpaceWrapperRef"
      class="operation-space-wrapper"
      @mousedown.left="startMove"
      @mouseup.left="endMove"
      @mouseout="endMove"
      @mousemove="move"
      @dragenter="(event) => event.preventDefault()"
      @dragover="(event) => event.preventDefault()"
      :class="{ 'operation-space-wrapper-move': inMoving }"
    >
      <el-scrollbar
        ref="operationSpaceScrollbarRef"
        x-scrollable
        @scroll="scroll"
        :style="{
          maxHeight: operationSpaceVisibleAreaSize.width + 'px',
          maxWidth: operationSpaceVisibleAreaSize.height + 'px'
        }"
      >
        <div
          style="position: relative"
          :style="{
            height: 2000 * operationSpaceZoom + 'px',
            width: 2000 * operationSpaceZoom + 'px'
          }"
        >
          <div
            ref="operationSpaceRef"
            class="operation-space"
            @click.self="dataFlowState.clearSelectedNode()"
            @contextmenu.self="showOperationSpaceContextmenu"
            :style="{
              left: (2000 * (operationSpaceZoom - 1)) / 2 + 'px',
              top: (2000 * (operationSpaceZoom - 1)) / 2 + 'px'
            }"
          >
            <operator-node4-operation-space
              v-for="item in dataFlowState.nodes"
              :key="item[0]"
              :id="item[0]"
              :uuid="item[0]"
              :name="item[1].name"
              :type="item[1].type"
              style="position: absolute"
            />
            <div id="select-area" ref="selectAreaRef" v-show="showSelectArea" class="select-area">
              <svg width="100%" height="100%">
                <rect
                  x="0"
                  y="0"
                  width="100%"
                  height="100%"
                  fill="none"
                  stroke="black"
                  stroke-width="2"
                ></rect>
              </svg>
            </div>
          </div>
          <OperationContextmenu
            v-model="contextmenu.show"
            ref="contextmenuRef"
            v-show="contextmenu.show"
            style="position: absolute"
            :type="contextmenu.type"
            :style="{
              left: contextmenu.left + 'px',
              top: contextmenu.top + 'px'
            }"
            :operation="getContextmenuOperation()"
            :set-zoom="setZoom"
          />
        </div>
      </el-scrollbar>
      <operation-space-tool-bar
        v-model:zoom="operationSpaceZoom"
        :set-zoom="setZoom"
        style="position: absolute; top: 10px; right: 15px"
      />
    </div>
    <node-configuration :node-id="currentNodeId" v-model="showNodeConfiguration" />
  </div>
</template>

<script setup lang="ts">
import { nextTick, onMounted, ref } from 'vue'
import { useElementSize, watchDebounced } from '@vueuse/core'
import OperationSpaceToolBar from '@/views/main/data-flow/design/components/OperationSpaceToolBar.vue'
import {
  BrowserJsPlumbInstance,
  Connection,
  ConnectionEstablishedParams,
  EVENT_CONNECTION,
  EVENT_CONNECTION_CONTEXTMENU,
  EVENT_ELEMENT_CONTEXTMENU,
  EVENT_ELEMENT_DBL_CLICK,
  EVENT_ELEMENT_MOUSE_DOWN,
  newInstance
} from '@jsplumb/browser-ui'
import {
  operatorTypeInfoMapping,
  generateBrowserJsPlumbDefaults,
  initJsplumbInstance,
  useKeyboardAction,
  useOperationSpaceDrag,
  useSelectArea
} from '@/views/main/data-flow/design/index'
import OperatorTypeNode4List from '@/views/main/data-flow/design/components/OperatorTypeNode4List.vue'
import OperationContextmenu from '@/views/main/data-flow/design/components/operation-contextmenu/Index.vue'
import NodeConfiguration from '@/views/main/data-flow/design/components/node-configuration/Index.vue'
import { useContextMenu } from '@/views/main/data-flow/design/components/operation-contextmenu'
import { useDataFlowState } from '@/model/data-flow/data-flow-state'
import { useRoute } from 'vue-router'
import {
  addNode,
  copyNode,
  deleteNode,
  pasteNode
} from '@/views/main/data-flow/design/node-operation'
import OperatorNode4OperationSpace from '@/views/main/data-flow/design/components/OperatorNode4OperationSpace.vue'
import { onKeyboardPressAndHold } from '@/utils/common-util'

const operationSpaceWrapperRef = ref()
const operationSpaceScrollbarRef = ref()
const operationSpaceRef = ref()

const operationSpaceVisibleAreaSize = useElementSize(operationSpaceWrapperRef)

const control = onKeyboardPressAndHold('Control')

const { dataFlowState } = useDataFlowState()

const currentRoute = useRoute()

//<--画布移动与拖动
const { inMoving, operationSpaceZoom, startMove, move, endMove, scroll } = useOperationSpaceDrag(
  operationSpaceWrapperRef,
  operationSpaceScrollbarRef
)

const jsplumbInstance = ref<BrowserJsPlumbInstance>()

//快捷键
const { ctrlA, ctrlC, ctrlV } = useKeyboardAction()
watchDebounced(
  ctrlA,
  () => {
    dataFlowState.selectAllNode()
  },
  { debounce: 500, maxWait: 200 }
)
watchDebounced(
  ctrlC,
  () => {
    console.log(1)
    copyNode(jsplumbInstance, dataFlowState)
  },
  { debounce: 500, maxWait: 200 }
)
watchDebounced(
  ctrlV,
  () => {
    pasteNode(jsplumbInstance, operationSpaceRef, dataFlowState, operationSpaceZoom.value)
  },
  { debounce: 500, maxWait: 200 }
)

//<--画布缩放
//type: 0 直接赋值;1 增加值;2 减少值
function setZoom(zoom: number, type: number = 0) {
  if (type === 0) {
    operationSpaceZoom.value = zoom / 100
  } else if (type === 1) {
    operationSpaceZoom.value = Math.min(operationSpaceZoom.value + zoom / 100, 1.5)
  } else {
    operationSpaceZoom.value = Math.max(operationSpaceZoom.value - zoom / 100, 0.7)
  }
  operationSpaceZoom.value = Number(operationSpaceZoom.value.toFixed(2))
  operationSpaceRef.value.style.transform = 'scale(' + operationSpaceZoom.value + ')'
  jsplumbInstance.value?.setZoom(operationSpaceZoom.value)
}

//画布缩放-->

//<--算子节点拖拽
const dragStartOffset = ref({
  left: 0,
  top: 0
})

function dragStart(event: MouseEvent) {
  const targetRect = (event.target as any).getBoundingClientRect()
  dragStartOffset.value.top = event.clientY - targetRect.top
  dragStartOffset.value.left = event.clientX - targetRect.left
}

function addFunctionTypeNode2OperationSpace(event: MouseEvent) {
  addNode(
    operationSpaceZoom.value,
    dragStartOffset.value,
    event,
    operationSpaceRef,
    jsplumbInstance,
    dataFlowState
  )
}

//算子节点拖拽-->

//<--右键菜单
const contextmenuRef = ref()
const {
  contextmenu,
  showOperationSpaceContextmenu,
  showConnectorContextmenu,
  showNodeContextmenu
} = useContextMenu(contextmenuRef, operationSpaceRef, operationSpaceWrapperRef)

function getContextmenuOperation() {
  switch (contextmenu.value.type) {
    case 'node':
      return {
        delete: () => {
          deleteNode(operationSpaceRef, jsplumbInstance, dataFlowState)
          contextmenu.value.show = false
        },
        edit: () => {
          openNodeConfiguration((contextmenu.value.payload as HTMLElement).id)
          contextmenu.value.show = false
        },
        copy: () => {
          copyNode(jsplumbInstance, dataFlowState)
          contextmenu.value.show = false
        }
      }
    case 'operation-space':
      return {
        paste: () => {
          pasteNode(
            jsplumbInstance,
            operationSpaceRef,
            dataFlowState,
            operationSpaceZoom.value,
            contextmenu.value.event!
          )
          contextmenu.value.show = false
        }
      }
    case 'connector':
      return {
        delete: () => {
          const connection = contextmenu.value.payload as Connection
          jsplumbInstance.value?.deleteConnection(connection)
          dataFlowState.deleteConnector(connection.sourceId, connection.targetId)
          contextmenu.value.show = false
        }
      }
  }
}

//右键菜单-->

//<--节点参数配置
const showNodeConfiguration = ref(false)
const currentNodeId = ref()

function openNodeConfiguration(id: string) {
  currentNodeId.value = id
  showNodeConfiguration.value = true
}

//节点参数配置-->

//<--节点多选
const selectAreaRef = ref()
const { showSelectArea, bindSelectArea } = useSelectArea(selectAreaRef, jsplumbInstance)
//节点多选-->
onMounted(() => {
  //初始化流程数据
  dataFlowState.init(currentRoute.query.name as string)
  //创建jsPlump对象
  jsplumbInstance.value = newInstance(generateBrowserJsPlumbDefaults(operationSpaceRef.value))
  initJsplumbInstance(jsplumbInstance.value!)
  //绑定连线右键菜单
  jsplumbInstance.value?.bind(EVENT_CONNECTION_CONTEXTMENU, showConnectorContextmenu)
  //绑定节点右键菜单
  jsplumbInstance.value?.bind(
    EVENT_ELEMENT_CONTEXTMENU,
    (element: HTMLElement, event: PointerEvent) => {
      dataFlowState.selectOneNode(element.id || element.parentElement?.id)
      showNodeContextmenu(element, event)
    }
  )
  //绑定节点单击事件
  jsplumbInstance.value?.bind(EVENT_ELEMENT_MOUSE_DOWN, (element) => {
    if (control.pressAndHold.value) {
      dataFlowState.selectIncrementNode(element.id)
    } else {
      dataFlowState.selectOneNode(element.id)
    }
  })
  //绑定节点双击事件
  jsplumbInstance.value?.bind(EVENT_ELEMENT_DBL_CLICK, (element: HTMLElement) => {
    openNodeConfiguration(element.id)
  })

  jsplumbInstance.value?.bind(EVENT_CONNECTION, (params: ConnectionEstablishedParams) => {
    const flag = dataFlowState.addConnector(params.sourceId, params.targetId)
    if (!flag) {
      nextTick(() => {
        jsplumbInstance.value?.deleteConnection(params.connection)
      })
    }
  })
  //绑定选区
  bindSelectArea()
})
</script>

<style scoped lang="scss">
@import 'index.scss';
</style>
